import { Injectable, OnModuleDestroy } from '@nestjs/common';
import Redis, { type RedisOptions } from 'ioredis';
import { EnvUtil } from './env.util';

/**
 * Redis 配置接口
 */
export interface IRedisConfig {
  host?: string;
  port?: number;
  password?: string;
  db?: number;
  keyPrefix?: string;
  retryStrategy?: (times: number) => number | void;
  maxRetriesPerRequest?: number;
}

/**
 * Redis 工具类
 * 
 * 提供两种使用方式：
 * 1. 静态方法：手动创建 Redis 客户端（适合工具函数、脚本）
 * 2. 实例方法：自动管理 Redis 连接（适合 Service、Module，支持依赖注入）
 * 
 * @example
 * ```ts
 * // 方式 1：静态方法（手动管理连接）
 * const redis = RedisUtil.createClient();
 * await redis.set('key', 'value');
 * await redis.quit();
 * 
 * // 方式 2：实例方法（自动管理连接）
 * @Injectable()
 * class SomeService {
 *   constructor(private readonly redisUtil: RedisUtil) {}
 *   
 *   async someMethod() {
 *     await this.redisUtil.set('key', 'value');
 *     const value = await this.redisUtil.get('key');
 *   }
 * }
 * ```
 */
@Injectable()
export class RedisUtil implements OnModuleDestroy {
  private client?: Redis;

  // ============================================
  // 静态方法（手动管理连接）
  // ============================================

  /**
   * 创建 Redis 客户端
   * 
   * @param config Redis 配置（可选，默认从环境变量读取）
   * @returns Redis 客户端实例
   * 
   * @example
   * ```ts
   * // 使用默认配置（从环境变量）
   * const redis = RedisUtil.createClient();
   * 
   * // 使用自定义配置
   * const redis = RedisUtil.createClient({
   *   host: 'localhost',
   *   port: 6379,
   *   password: 'secret',
   *   db: 0,
   * });
   * ```
   */
  static createClient(config?: IRedisConfig): Redis {
    const options: RedisOptions = {
      host: config?.host ?? EnvUtil.get('REDIS_HOST', 'localhost'),
      port: config?.port ?? EnvUtil.getNumber('REDIS_PORT', 6379),
      password: config?.password ?? EnvUtil.getOptional('REDIS_PASSWORD'),
      db: config?.db ?? EnvUtil.getNumber('REDIS_DB', 0),
      keyPrefix: config?.keyPrefix,
      retryStrategy: config?.retryStrategy ?? ((times: number) => {
        // 最多重试 10 次
        if (times > 10) {
          return null;
        }
        // 重试延迟：min(times * 50, 2000) ms
        return Math.min(times * 50, 2000);
      }),
      maxRetriesPerRequest: config?.maxRetriesPerRequest ?? 3,
    };

    return new Redis(options);
  }

  /**
   * 从环境变量构建 Redis URL
   * 
   * @returns Redis 连接 URL
   * 
   * @example
   * ```ts
   * const url = RedisUtil.buildRedisUrl();
   * // redis://:password@localhost:6379/0
   * ```
   */
  static buildRedisUrl(): string {
    const host = EnvUtil.get('REDIS_HOST', 'localhost');
    const port = EnvUtil.getNumber('REDIS_PORT', 6379);
    const password = EnvUtil.getOptional('REDIS_PASSWORD');
    const db = EnvUtil.getNumber('REDIS_DB', 0);

    if (password) {
      return `redis://:${password}@${host}:${port}/${db}`;
    }
    return `redis://${host}:${port}/${db}`;
  }

  /**
   * 测试 Redis 连接
   * 
   * @param config Redis 配置（可选）
   * @returns 是否连接成功
   * 
   * @example
   * ```ts
   * const isConnected = await RedisUtil.testConnection();
   * if (isConnected) {
   *   console.log('Redis 连接成功');
   * }
   * ```
   */
  static async testConnection(config?: IRedisConfig): Promise<boolean> {
    const redis = RedisUtil.createClient(config);
    try {
      await redis.ping();
      return true;
    } catch (error) {
      return false;
    } finally {
      await redis.quit();
    }
  }

  // ============================================
  // 实例方法（自动管理连接）
  // ============================================

  /**
   * 获取 Redis 客户端实例
   * 
   * @private
   * @returns Redis 客户端
   */
  private getClient(): Redis {
    if (!this.client) {
      this.client = RedisUtil.createClient();
    }
    return this.client;
  }

  /**
   * 模块销毁时关闭连接
   */
  async onModuleDestroy() {
    if (this.client) {
      await this.client.quit();
    }
  }

  // ============================================
  // 基础操作
  // ============================================

  /**
   * 设置键值
   * 
   * @param key 键
   * @param value 值
   * @param ttl 过期时间（秒，可选）
   * 
   * @example
   * ```ts
   * await this.redisUtil.set('user:123', JSON.stringify(user));
   * await this.redisUtil.set('token:abc', 'valid', 3600); // 1小时后过期
   * ```
   */
  async set(key: string, value: string, ttl?: number): Promise<void> {
    const client = this.getClient();
    if (ttl) {
      await client.setex(key, ttl, value);
    } else {
      await client.set(key, value);
    }
  }

  /**
   * 获取键值
   * 
   * @param key 键
   * @returns 值或 null
   * 
   * @example
   * ```ts
   * const value = await this.redisUtil.get('user:123');
   * if (value) {
   *   const user = JSON.parse(value);
   * }
   * ```
   */
  async get(key: string): Promise<string | null> {
    const client = this.getClient();
    return client.get(key);
  }

  /**
   * 删除键
   * 
   * @param keys 键（支持多个）
   * @returns 删除的键数量
   * 
   * @example
   * ```ts
   * await this.redisUtil.del('user:123');
   * await this.redisUtil.del('user:123', 'user:456');
   * ```
   */
  async del(...keys: string[]): Promise<number> {
    const client = this.getClient();
    return client.del(...keys);
  }

  /**
   * 检查键是否存在
   * 
   * @param key 键
   * @returns 是否存在
   * 
   * @example
   * ```ts
   * const exists = await this.redisUtil.exists('user:123');
   * ```
   */
  async exists(key: string): Promise<boolean> {
    const client = this.getClient();
    const result = await client.exists(key);
    return result === 1;
  }

  /**
   * 设置键的过期时间
   * 
   * @param key 键
   * @param seconds 过期时间（秒）
   * @returns 是否设置成功
   * 
   * @example
   * ```ts
   * await this.redisUtil.expire('user:123', 3600); // 1小时后过期
   * ```
   */
  async expire(key: string, seconds: number): Promise<boolean> {
    const client = this.getClient();
    const result = await client.expire(key, seconds);
    return result === 1;
  }

  /**
   * 获取键的剩余过期时间
   * 
   * @param key 键
   * @returns 剩余秒数（-1 表示永不过期，-2 表示键不存在）
   * 
   * @example
   * ```ts
   * const ttl = await this.redisUtil.ttl('user:123');
   * ```
   */
  async ttl(key: string): Promise<number> {
    const client = this.getClient();
    return client.ttl(key);
  }

  // ============================================
  // JSON 操作（便捷方法）
  // ============================================

  /**
   * 设置 JSON 对象
   * 
   * @param key 键
   * @param value 对象
   * @param ttl 过期时间（秒，可选）
   * 
   * @example
   * ```ts
   * await this.redisUtil.setJson('user:123', { name: 'John', age: 30 });
   * ```
   */
  async setJson<T = any>(key: string, value: T, ttl?: number): Promise<void> {
    await this.set(key, JSON.stringify(value), ttl);
  }

  /**
   * 获取 JSON 对象
   * 
   * @param key 键
   * @returns 对象或 null
   * 
   * @example
   * ```ts
   * const user = await this.redisUtil.getJson<User>('user:123');
   * ```
   */
  async getJson<T = any>(key: string): Promise<T | null> {
    const value = await this.get(key);
    if (!value) return null;
    try {
      return JSON.parse(value) as T;
    } catch {
      return null;
    }
  }

  // ============================================
  // 批量操作
  // ============================================

  /**
   * 批量获取键值
   * 
   * @param keys 键数组
   * @returns 值数组（null 表示键不存在）
   * 
   * @example
   * ```ts
   * const values = await this.redisUtil.mget('user:123', 'user:456');
   * ```
   */
  async mget(...keys: string[]): Promise<(string | null)[]> {
    const client = this.getClient();
    return client.mget(...keys);
  }

  /**
   * 根据模式删除键
   * 
   * @param pattern 模式（支持通配符 *）
   * @returns 删除的键数量
   * 
   * @example
   * ```ts
   * await this.redisUtil.delByPattern('user:*'); // 删除所有 user: 开头的键
   * ```
   */
  async delByPattern(pattern: string): Promise<number> {
    const client = this.getClient();
    const keys = await client.keys(pattern);
    if (keys.length === 0) return 0;
    return client.del(...keys);
  }

  // ============================================
  // 高级操作
  // ============================================

  /**
   * 获取原始 Redis 客户端（用于高级操作）
   * 
   * @returns Redis 客户端实例
   * 
   * @example
   * ```ts
   * const client = this.redisUtil.getRedisClient();
   * await client.hset('hash:key', 'field', 'value');
   * ```
   */
  getRedisClient(): Redis {
    return this.getClient();
  }
}

